其實我們的 Mavericks 已經做得差不多了,但就是那個 View 總覺得還可以再更好,如果仔細看 Rails 的原始碼,會發現有一個叫 ActionView 的部分,是用來處理有關 Template render,既然這樣的話...,那最後兩天就來做這個部分吧!
在開始之前,我們要先建立一些觀念
經過將近一個月的實作,我們已經了解到 Rails 在實作 Controller 時會 new 一個 Controller 的物件出來,而每一個 Action 其實就是 Controller 的 mehtod,所以你會看到類似這樣的程式碼
# mavericks/lib/action_controller/metal.rb
module ActionController
class Metal
def process(action)
send action
end
# .
#.
# (略)
end
end
那 View 和 Controller 的關係又是什麼呢?我們都知道在 Ruby 裡面,一個物件裡面包含了 實體變數
,然後可以用物件的 實體方法
來操作 實體變數
,這樣的觀念相信大家都不陌生
就像下面的例子
class A
def initialize(name)
@name = name
end
def call
@name
end
end
puts A.new('apa').call
# apa
所以你可以這樣想像
實體變數
時,我們可以將這個 實體變數
先取出來然後放到 View 的 實體變數
裡面實體方法
實體方法
時,自然就會代入 實體變數
有點複雜?沒關係,所以這裡這裡才要花上兩天實作...XD,那我們先來搞清楚怎麼將 Controller 的 實體變數
取出來,放到 View 的 實體變數
裡面,這裡用的是 Ruby 的 instance_variables
,透過這個 method 我們可以將這個物件的 實體變數
給列出來
像是這樣
# demo.rb
class Controller
def initialize(name)
@name = name
end
def index
@name
end
end
puts Controller.new('apa').instance_variables
# @name
取出來後要怎麼放到 View 的物件裡面呢?我們可以先搭配 instance_variable_get
來取得值,並且轉換成 Hash
像是這樣
# demo.rb
class TasksController
def initialize(name)
@name = name
end
def index
@name
end
def render
assigns = {}
instance_variables.each do |name|
assigns[name[1..-1]] = instance_variable_get(name)
end
assigns
end
end
puts TasksController.new('apa').render
# {"name"=>"apa"}
再用另外一個很像的方法,叫 instance_variable_set
,來存放到 View 的物件裡面,
# demo.rb
class ActionView
def initialize(assigns = {})
assigns.each_pair do |name, value|
instance_variable_set "@#{name}", value
end
end
end
class TasksController
def initialize(name)
@name = name
end
def index
@name
end
def render
assigns = {}
instance_variables.each do |name|
assigns[name[1..-1]] = instance_variable_get(name)
end
assigns
end
end
controller_instance_variable = TasksController.new('apa').render
action_view = ActionView.new(controller_instance_variable)
puts action_view.instance_variables
# @name
puts action_view.instance_variable_get(:@name)
# apa
這樣我們就有步驟 1 的概念了
我們在步驟 2 有提到要將 .erb
檔案裡面的程式碼,轉成 View 的 實體方法
來執行,該怎麼做?我們可以用 include
搭配 module_eval
來實作到這點
一樣直接看範例
# demo.rb
module CompiledTemplates
end
class Base
def compiled(method_name, code)
CompiledTemplates.module_eval <<-CODE
def #{method_name}
#{code}
end
CODE
end
end
class Template
include CompiledTemplates
end
Base.new.compiled('index_template', "'<h1>I am apa</h1>'")
puts Template.new.index_template
# <h1>I am apa</h1>
我們建立一個空的 Module,接著實作一個 Base
,裡面有一個方法用來將 .erb
Template 裡面的程式碼轉成另一個 Class 的 實體方法
,用法就是裡面 module_eval
來「增加」CompiledTemplates 裡面的 mehtod,接著 Template include 以後,就可以做到動態增加 instance method 的效果
有沒有發現 module_eval
和前面所提到的 class_eval
和 instance_eval
很像?如果不清楚的話,建議透過多看幾次官方的範例,來了解三者之間的差異
接著我們將前面兩個步驟合在一起
# demo.rb
module CompiledTemplates
end
class Base def compiled(method_name, code)
CompiledTemplates.module_eval <<-CODE
def #{method_name}
#{code}
end
CODE
end
end
class ActionView
include CompiledTemplates
def initialize(assigns = {})
assigns.each_pair do |name, value|
instance_variable_set "@#{name}", value
end
end
end
class TasksController
def initialize(name)
@name = name
end
def index
@name
end
def render
assigns = {}
instance_variables.each do |name|
assigns[name[1..-1]] = instance_variable_get(name)
end
assigns
end
end
Base.new.compiled('index_template', "\"<h1>I am \#{@name}</h1>\"")
controller_instance_variable = TasksController.new('apa').render
action_view = ActionView.new(controller_instance_variable)
puts action_view.index_template
# <h1>I am apa</h1>
就完成 Rails 的 Template render 了!帶著這個觀念,明天就來實作在 Mavericks 上吧!